home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
C/C++ Interactive Reference Guide
/
C-C++ Interactive Reference Guide.iso
/
c_ref
/
csource5
/
357_01
/
cstar1.exe
/
DIR.C
< prev
next >
Wrap
C/C++ Source or Header
|
1991-06-18
|
18KB
|
939 lines
/*
C* preprocessor -- directives
source: dir.c
started: October 7, 1985
version:
August 8, 1986
February 20, 1989: added multiple path support to pp_include
March 8, 1989
PUBLIC DOMAIN SOFTWARE
The CSTAR program was placed in the public domain on June 15, 1991,
by its author and sole owner,
Edward K. Ream
1617 Monroe Street
Madison, WI 53711
(608) 257-0802
CSTAR may be used for any commercial or non-commercial purpose.
See cstar.h or cstar.c for a DISCLAIMER OF WARRANTIES.
*/
#include "cstar.h"
/*
Externally visible routines.
*/
void do_pp(void);
/*
Internal routines.
*/
static int eval (void);
static int prec (int operator);
static int gt_prec (int opr1, int opr2);
static bool isfnch (int c, int delim);
static int pop_op (void);
static int pop_val (void);
static void psh_op (int op);
static void push_val (int val);
static void pp_else (void);
static void pp_endif (void);
static void pp_enum (void);
static void pp_if (void);
static void pp_ifdef (bool flag);
static void pp_incl (void);
static void pp_line (void);
static void pp_undef (void);
static void skip_lines (void);
/*
The pp_def() routine, which handles #define directives, replaces the
i'th formal argument in the replacement string by the ARG_FLAG
character followed by i + ARG_OFFSET. The pp_expand() routine, which
handles macro calls, replaces these two characters by the i'th actual
argument.
The ARG_FLAG character should be a character that can never appear in
normal text, but it MUST NOT BE NEGATIVE, so as to fit in a char.
*/
#define ARG_FLAG '$'
#define ARG_OFFSET '0'
/*
Do one preprocessor directive.
*/
#define EQL(string) str_eq(t_symbol+1,string+1)
void
do_pp(void)
{
char msg[100];
TICK("do_pp");
/* Get the directive into t_symbol[]. */
if (!isalpha(ch)) {
goto not_alpha;
}
t_id(t_symbol);
/* Skip blanks after the directive. */
skip_bl();
switch(t_symbol [0]) {
case 'd':
if (t_length == 6 && EQL("define")) {
pp_def();
return;
}
goto not_pp;
case 'e':
if (t_length == 4 && EQL("else")) {
pp_else();
return;
}
if (t_length == 4 && EQL("enum")) {
pp_enum();
return;
}
else if (t_length == 5 && EQL("endif")) {
pp_endif();
return;
}
goto not_pp;
case 'i':
switch(t_length) {
case 2: if (EQL("if")) {
pp_if();
return;
}
goto not_pp;
case 5: if (EQL("ifdef")) {
pp_ifdef(TRUE);
return;
}
goto not_pp;
case 6: if (EQL("ifndef")) {
pp_ifdef(FALSE);
return;
}
goto not_pp;
case 7: if (EQL("include")) {
pp_incl();
return;
}
goto not_pp;
}
goto not_pp;
case 'l':
if (t_length == 4 && EQL("line")) {
pp_line();
return;
}
goto not_pp;
case 'p':
if (t_length == 6 && EQL("pragma")) {
/* Do NOTHING!! */
skip_pp();
return;
}
goto not_pp;
case 'u':
if (t_length == 5 && EQL("undef")) {
pp_undef();
return;
}
goto not_pp;
default:
goto not_pp;
}
not_alpha:
/*
Be more permissive than the new C standard.
Just skip the rest of the line.
*/
skip_pp();
return;
not_pp:
strcpy(msg, t_symbol);
strcat(msg, " is not a valid preprocessor directive.");
t_error(msg);
skip_pp();
}
#undef EQL
/*
Define a list of constants.
#enum(constant, id1, id2, ... , idn)
*/
static void
pp_enum(void)
{
register int flag;
register long val;
char symbol [100];
char txt [LONG_DIGITS];
TICK("pp_enum");
/* Get opening '(' */
skip_ws();
if (ch == '(') {
sysnext();
}
else {
goto bad_ch;
}
/* Get initial constant. */
if (ch < '0' || ch > '9') {
goto bad_ch;
}
(void) t_number();
val = t_value;
flag = FALSE;
for (;;) {
skip_ws();
if (ch == ')' ) {
skip_pp();
return;
}
if (ch == ',') {
sysnext();
if (flag == TRUE) {
val++;
}
flag = TRUE;
continue;
}
if (ch == '\\') {
sysnext();
if (ch == '\r') {
sysnext();
}
if (ch == '\n') {
sysnext();
do_nl();
begin_line(FALSE);
continue;
}
else {
goto bad_ch;
}
}
if (flag == FALSE || !isid1(ch)) {
goto bad_ch;
}
/* Get the symbol into symbol[]. */
t_id(symbol);
/* Look for '=' constant. */
skip_bl();
if (ch == '=') {
sysnext();
skip_ws();
/* Get constant into val. */
if (ch < '0' || ch > '9') {
goto bad_ch;
}
(void) t_number();
val = t_value;
}
/* Enter the symbol with no arguments. */
convl2s(val, txt);
(void) mst_enter(symbol, txt, -1);
/* Bump val and indicate a comma is expected. */
val++;
flag = FALSE;
}
bad_ch: t_error("ill formed #enum");
skip_pp();
}
/*
Evaluate a constant expression to either true or false.
A constant expression consists of:
1. integer constants or character constants
2. the unary - + and ~ operators
3. the binary + - * / & | ^ << >> == != < > <= >= oprators
4. the ternary ? : operator
Identifiers are expanded if they are defined, otherwise they
are taken to have a value of zero.
The code below evaluates constant expressions using an operator
precedence algorithm which employs an operator stack and an
operand stack. The global t_evalstk[] array houses both stacks.
The operator stack starts at t_evalstk[0] and grows up.
The operand stack starts at t_evalstk[MAX_EVAL-1] and grows down.
The globals t_opptr and t_valptr are point at the top of each stack.
As usual, stack overflow happens when these two pointers "cross."
Several auxilliary routines are used to handle these stacks:
psh_op(), pop_op(), push_val() and pop_val();
*/
static int
eval(void)
{
register int op, op2, token, val1, val2, val3;
TICK("eval");
/* Initialize the operator and operand stacks. */
t_opptr = -1;
t_valptr = MAX_EVAL;
/* State S1: unary +, unary -, ~, constant or id is expected here. */
s1:
token = con_token();
/* Push all unary ops. */
if (token == PLUS_TOK) {
psh_op(UPLUS_TOK);
goto s1;
}
else if (token == MINUS_TOK) {
psh_op(UMINUS_TOK);
goto s1;
}
else if (token == TILDE_TOK) {
psh_op(TILDE_TOK);
goto s1;
}
/* We expect a constant or identifier here. */
if (token == INT_TOK || token == CHAR_TOK) {
push_val((int) t_value);
}
else if (token == ID_TOK) {
push_val(0);
}
else {
goto bad_expr;
}
/* Perform all unary ops and enter state S2. */
while (t_opptr >= 0) {
switch (op = pop_op()) {
case UPLUS_TOK: break;
case UMINUS_TOK: push_val(-pop_val()); break;
case TILDE_TOK: push_val(~pop_val()); break;
default: psh_op(op); goto s2;
}
}
/* State S2: binary op or end_of_expression expected here. */
s2:
token = con_token();
/*
Perform binary operators until the operator stack is
empty or until token operator has a higher precedence
than the operator on the top of the operator stack.
*/
while (t_opptr >= 0 && gt_prec(t_evalstk[t_opptr], token)) {
val2 = pop_val();
val1 = pop_val();
op = pop_op();
switch (op) {
case PLUS_TOK: push_val(val1 + val2); break;
case MINUS_TOK: push_val(val1 - val2); break;
case STAR_TOK: push_val(val1 * val2); break;
case DIV_TOK: push_val(val2 ? (val1/val2) : 0);
break;
case MOD_TOK: push_val(val1 % val2); break;
case AND_TOK: push_val(val1 & val2); break;
case OR_TOK: push_val(val1 | val2); break;
case XOR_TOK: push_val(val1 ^ val2); break;
case LSHIFT_TOK: push_val(val1 << val2); break;
case RSHIFT_TOK: push_val(val1 >> val2); break;
case EQUAL_TOK: push_val(val1 == val2); break;
case NE_TOK: push_val(val1 != val2); break;
case LT_TOK: push_val(val1 < val2); break;
case GT_TOK: push_val(val1 > val2); break;
case LE_TOK: push_val(val1 <= val2); break;
case GE_TOK: push_val(val1 >= val2); break;
case COLON_TOK: op2 = pop_op();
if (op2 != QUESTION_TOK) {
goto bad_expr;
}
val3 = pop_val();
push_val(val3 ? val1 : val2);
break;
default: goto bad_expr;
}
}
/* Enter state S1 or return on end-of-expression. */
if (token == ZERO) {
skip_pp();
val1 = pop_val();
return val1;
}
else {
psh_op(token);
goto s1;
}
bad_expr:
t_error("bad constant expression--zero assumed");
skip_pp();
return 0;
}
/*
Return TRUE if opr1 has higher precedence than opr2.
If opr1 and opr2 have the same precedence, return TRUE
if they associate left to right.
This code reflects the table on page 49 of K & R.
*/
static int
gt_prec(register int opr1, register int opr2)
{
register int prec1, prec2;
TICK("gt_prec");
prec1 = prec(opr1);
prec2 = prec(opr2);
if (prec1 != prec2) {
/* Associativity doesn't matter. */
return prec1 > prec2;
}
else if (prec1 == 14 || prec1 == 3 || prec1 == 2) {
/* Associate right to left. */
return FALSE;
}
else {
/* Associate left to right. */
return TRUE;
}
}
/*
Return TRUE if c is legal in a file name.
Assume that the delim character is not legal.
*/
static bool
isfnch(int c, int delim)
{
TICK("isfnch");
switch (c) {
case '*':
case '?':
case '\n':
case '\r':
case ' ':
case '\t':
case END_FILE:
return FALSE;
default: return c != delim;
}
}
/*
Return the precedence of an operator.
This code reflects the table on page 49 of K & R.
*/
static int
prec(int operator)
{
TICK("prec");
switch (operator) {
case TILDE_TOK: return 14;
case STAR_TOK:
case DIV_TOK:
case MOD_TOK: return 13;
case PLUS_TOK:
case MINUS_TOK: return 12;
case LSHIFT_TOK:
case RSHIFT_TOK: return 11;
case LT_TOK:
case LE_TOK:
case GT_TOK:
case GE_TOK: return 10;
case EQUAL_TOK:
case NE_TOK: return 9;
case XOR_TOK: return 7;
case OR_TOK: return 6;
case COLON_TOK:
case QUESTION_TOK: return 3;
case ZERO:
case ERROR: return ZERO;
default: return ERROR;
}
}
/*
Routines to push and pop the operator and operand stacks.
*/
static void
psh_op(int op)
{
TICK("psh_op");
if (t_opptr + 1 < t_valptr) {
t_evalstk [++t_opptr] = op;
return;
}
fatal("psh_op: overflow\n");
}
static int
pop_op(void)
{
TICK("pop_op");
if (t_opptr >= 0) {
return t_evalstk [t_opptr--];
}
fatal("pop_op: underflow\n");
}
static void
push_val(int val)
{
TICK("push_val");
TRACE("push_val", printf("push_val %d\n", val));
if (t_valptr - 1 > t_opptr) {
t_evalstk [--t_valptr] = val;
return;
}
fatal("push_val: overflow\n");
}
static int
pop_val(void)
{
TICK("pop_val");
if (t_valptr < MAX_EVAL) {
TRACE("pop_val", printf("pop_val %d\n", t_evalstk[t_valptr]));
return t_evalstk [t_valptr++];
}
fatal("pop_val: underflow\n");
}
/*
Handle the #else directive.
*/
static void
pp_else(void)
{
TICK("pp_else");
if (t_iflevel == 0) {
t_error("#else ignored--no matching #if.");
skip_pp();
}
else if (t_ifstack [t_iflevel - 1] == TRUE) {
t_error("Duplicate #else ignored.");
skip_pp();
}
else {
t_ifstack [t_iflevel - 1] = TRUE;
skip_lines();
}
}
/*
Handle the #endif directive.
*/
static void
pp_endif(void)
{
TICK("pp_endif");
if (t_iflevel == 0) {
t_error("#endif ignored--no matching #if.");
skip_pp();
}
else {
t_iflevel--;
skip_pp();
}
}
/*
Handle the #if directive.
*/
static void
pp_if(void)
{
TICK("pp_if");
/* Indicate that no #else has been seen. */
if (t_iflevel >= MAX_IF) {
t_error("#if ignored--nested too deeply.");
skip_pp();
return;
}
t_ifstack [t_iflevel++] = FALSE;
/* Skip lines if the constant expression evaluates to zero. */
if (eval() == 0) {
/* Skip until #else or #endif. */
skip_lines();
}
}
/*
Handle the #ifdef and #ifndef directives.
flag == TRUE: #ifdef
flag == FALSE: #ifndef
*/
static void
pp_ifdef(int flag)
{
register struct mst_node * p;
struct mst_node * mst_lookup();
TICK("pp_ifdef");
if(t_iflevel > MAX_IF) {
t_error("#ifdef or #ifndef ignored--nested too deeply.");
skip_pp();
return;
}
if (!isid1(ch)) {
t_error("#ifdef or #ifndef ignored--identifier expected.");
skip_pp();
return;
}
/* Get id into t_symbol[]. */
t_id(t_symbol);
skip_pp();
/* Indicate that no #else has been seen. */
t_ifstack [t_iflevel++] = FALSE;
/* Skip lines if required. */
p = mst_lookup(t_symbol);
if ((flag && p == NULL) || (!flag && p != NULL)) {
/* Skip until #else or #endif. */
skip_lines();
}
}
/*
Handle the #include directive.
*/
static void
pp_incl(void)
{
int err_flag;
char mess [100];
register int i, j;
unsigned char f_name [MAX_FILE_NAME];
unsigned char buffer [MAX_FILE_NAME];
unsigned char path_name [200];
unsigned char delim;
TICK("pp_incl");
/* File name is OK. */
err_flag = FALSE;
/* Check for opening delimiter. */
if (ch == '"') {
delim = '"';
sysnext();
}
else if (ch == '<') {
delim = '>';
sysnext();
}
else {
err_flag = TRUE;
delim = '\r';
}
/* Get file name into f_name[]. */
for (i = 0; i < MAX_FILE_NAME-1 && isfnch(ch, delim); i++) {
f_name[i] = ch;
sysnext();
}
f_name[i] = '\0';
if (err_flag || ch != delim) {
strcpy(mess, "#include ");
strcat(mess, f_name);
strcat(mess, " ignored--bad delimiter.");
t_error(mess);
skip_pp();
return;
}
/* Skip over the delimiter. */
sysnext();
/*
Skip the line, including the newline character.
Do NOT call skip_past(), because we don't want to
write the old include-level mark here. We also
don't want to write the NEW include-level mark here
because we don't know whether sysopen() will fail.
We MUST, however, skip the line here, because after
sysopen() is called we would not be able to skip
characters in the old level.
*/
skip_pp();
if (ch == '\n') {
sysnext();
}
do_nl();
if (t_inlevel >= MAX_INCLUDE) {
strcpy(mess, "#include ");
strcat(mess, f_name);
strcat(mess, " ignored--nested too deeply.");
t_line--;
t_error(mess);
t_line++;
/* Add the include level mark. */
begin_line(TRUE);
return;
}
/* Open the file. */
if(sysopen(f_name) == FALSE) {
for (i = 0; i < n_paths; i++) {
strcpy(path_name, paths [i]);
strcat(path_name, f_name);
TRACEP("pp_incl", printf("attempting to open %s\n",
path_name));
if (sysopen(path_name) == TRUE) {
return;
}
}
strcpy(mess, "#include ");
strcat(mess, f_name);
strcat(mess, " ignored--file not found.");
t_line--;
t_error(mess);
t_line++;
}
begin_line(TRUE);
}
/*
Set the line number and file name.
*/
static void
pp_line(void)
{
register int i;
TICK("pp_line");
(void) t_number();
t_line = CAST(int) t_value;
/* See if an optional file name is present. */
skip_bl();
if (ch == '\n' || ch == '\r' || ch == END_FILE) {
return;
}
/* Copy line to t_file[]. */
for(i = 0; i < MAX_FILE_NAME - 1; i++) {
if (ch == END_FILE || ch == '\n' || ch == '\r') {
break;
}
t_file[i] = CAST(char) ch;
sysnext();
}
t_file [i] = '\0';
skip_pp();
}
/*
Undefine a previously #defined variable.
*/
static void
pp_undef(void)
{
TICK("pp_undef");
/* Get the identifier into t_symbol[]. */
if (!isid1(ch)) {
t_error("#undef ignored--identifier expected.");
skip_pp();
return;
}
t_id(t_symbol);
/* Delete the identifier. Give no warning if it doesn't exist. */
mst_delete(t_symbol);
skip_pp();
}
/*
Skip lines until a matching #else or #endif directive is found.
Thus, interior #ifdef, #ifndef, #if and #else directives must
be recognized and dealt with.
A fine point: This code skips lines without doing any parsing of the
line. The ONLY things this code looks for are lines which start with
#ifdef, #ifndef, #if or #else. One should not detect "unknown"
preprocessor directives or other possible "errors," for the very good
reason that one wants to use conditional compilation to exclude lines
which might not even be written in the C language.
Examples can be given where this action might be suspect. Consider a
string which is continued from one line to the next using the backslash
newline convention. If the continuation line starts with one of the
four directives that this routine is looking for then either the lex or
the person using the lex will become confused. This is a minor hole in
the C language, and it seems better to ignore the fine points and do
what is sensible in most cases, namely skip entire lines as a unit.
The routine skip_past() is used to do the skipping of exactly 1 line.
*/
#define EQL(string) str_eq(t_symbol,string)
static void
skip_lines(void)
{
register int level;
TICK("skip_lines");
/* Just in case. */
if (t_iflevel <= 0) {
fatal("skip_lines: can't happen.");
}
/* No inner nexting yet. */
level = 0;
/* Start a new line. */
loop:
TICK("skip_lines1");
if (ch == END_FILE) {
t_error("File ends inside range of #ifdef.");
return;
}
if (ch == '\r') {
sysnext();
goto loop;
}
/* Skip the line if it doesn't start with '#'. */
if (ch != '#') {
skip_past();
goto loop;
}
/* Skip the line if '#' isn't followed by an id. */
sysnext();
if (!isid1(ch)) {
skip_past();
goto loop;
}
/* Get the directive into t_symbol[]. */
t_id(t_symbol);
if (EQL("ifdef") || EQL("ifndef") || EQL("if")) {
level++;
}
else if (EQL("else")) {
if (level > 0) {
;
}
else if (t_ifstack [t_iflevel - 1] == FALSE) {
t_ifstack [t_iflevel - 1] = TRUE;
skip_pp();
return;
}
else {
t_error("Extra #else ignored.");
}
}
else if (EQL("endif")) {
if (level > 0) {
level--;
}
else {
t_iflevel--;
skip_pp();
return;
}
}
else {
skip_past();
}
goto loop;
}
#undef EQL